// Copyright (c) Microsoft Corporation. All rights reserved.

using System;
using System.Collections.Generic;
using System.Text;
using System.Xml.XPath;
using System.IO;
using System.Xml.Serialization;
using Microsoft.TeamFoundation.Migration.Toolkit.VC;
using System.Collections.ObjectModel;
using System.Globalization;
using System.Diagnostics;
using Microsoft.TeamFoundation.Client;

namespace Microsoft.TeamFoundation.Migration.Toolkit
{
    /// <summary>
    /// The core version control configuration object.  This comprises the inside of the "VC" node within
    /// the migration configuration xml document.
    /// </summary>
    public class VersionControlConfiguration : SettingsSection, IMigrationProvider
    {
        /// <summary>
        /// The sessions associated with this configuration.
        /// </summary>
        public Dictionary<string, VersionControlSession> Sessions
        {
            get
            {
                return m_sessions;
            }
        }


        /// <summary>
        /// Loads a new VersionControlConfiguration instance from the provided XPath navigator.
        /// </summary>
        /// <param name="node">The configuration Xml</param>
        /// <returns>A VersionControlConfiguration instance</returns>
        public static VersionControlConfiguration Load(XPathNavigator node)
        {
            VersionControlConfiguration config = new VersionControlConfiguration();

            XPathNodeIterator sessions = node.Select("//VC/Sessions/Session");
            foreach (XPathNavigator sessionXml in sessions)
            {
                VersionControlSession session = LoadVCSession(sessionXml);
                config.Sessions.Add(session.Id, session);
            }

            try
            {
                validateVCSessionWorkspacesAreUnique(config.Sessions);

                // Load the VC settings
                XPathNodeIterator vcSettings = node.Select("//VC/Settings/Setting");
                config.InitFromXml(vcSettings);
            }
            catch (Exception e)
            {
                TraceManager.TraceException(e);

                // if we don't dispose here we can leave some resources locked which is not a problem
                // if we're terminating the application but does present problems for apps running
                // in service mode and tests.
                config.Dispose();
                throw;
            }
    
            return config;
        }

        /// <summary>
        /// Ensures that the VC sessions refer to unique file system and TFS workspace locations.
        /// </summary>
        /// <param name="dictionary"></param>
        private static void validateVCSessionWorkspacesAreUnique(Dictionary<string, VersionControlSession> dictionary)
        {
            if (dictionary.Count <= 1)
            {
                return;
            }

            string[] keys = new string[dictionary.Count];
            dictionary.Keys.CopyTo(keys, 0);

            for (int i = 0; i < keys.Length; i++)
            {
                VersionControlSession left = dictionary[keys[i]];

                for (int j = i + 1; j < keys.Length; j++)
                {
                    VersionControlSession right = dictionary[keys[j]];

                    // Compare the workspace root paths.  Do this by normalizing the paths
                    // using the DirectoryInfo class.
                    DirectoryInfo diLeft = new DirectoryInfo(left.Target.WorkspaceRoot);
                    DirectoryInfo diRight = new DirectoryInfo(right.Target.WorkspaceRoot);

                    if (StringComparer.InvariantCultureIgnoreCase.Compare(diLeft.FullName, diRight.FullName) == 0)
                    {
                        throw new InitializationException(
                            string.Format(
                            MigrationToolkitVCResources.Culture,
                            MigrationToolkitVCResources.VCWorkspaceRootConflict,
                            left.Id, right.Id)
                        );
                    }

                    // Compare the workspace names.  If the names are the same then make sure
                    // that they refer to different TFS servers (or throw if they are the same)
                    if (StringComparer.InvariantCultureIgnoreCase.Compare(
                        left.Target.Workspace,
                        right.Target.Workspace) == 0)
                    {
                        bool sameServer = false;

                        // first check the server names.  If they are the same then we're on the same
                        // server and can avoid round-trips to the server to find this out.
                        if (StringComparer.InvariantCultureIgnoreCase.Compare(
                            left.Target.Server, right.Target.Server) != 0)
                        {
                            // since the servers could be represented as short names or complete urls
                            // or even secure urls we need to get the TFS server ID
                            TeamFoundationServer tfsLeft = new TeamFoundationServer(left.Target.Server);
                            TeamFoundationServer tfsRight = new TeamFoundationServer(right.Target.Server);

                            sameServer = tfsLeft.InstanceId.Equals(tfsRight.InstanceId);
                        }
                        else
                        {
                            sameServer = true;
                        }

                        if(sameServer)
                        {
                            throw new InitializationException(
                                string.Format(
                                MigrationToolkitVCResources.Culture,
                                MigrationToolkitVCResources.VCWorkspaceConflict,
                                left.Id, right.Id));
                        }
                    }
                }
            }
        }

        private static VersionControlSession LoadVCSession(XPathNavigator xml)
        {
            VersionControlSession session = createVcSessionInstance(xml);

            // Get Session Id
            session.Id = GetAttributeFromCurrent(xml, "id", true);

            string sessionXpath = string.Format(CultureInfo.InvariantCulture, "Session[@id=\"{0}\"]", session.Id);

            // Load session settings
            XPathNodeIterator globalSettings = xml.Select(string.Format(CultureInfo.InvariantCulture, "//{0}/Settings/Setting", sessionXpath));
            session.InitFromXml(globalSettings);

            // Load mappings
            XPathNodeIterator mappings = xml.Select(string.Format(CultureInfo.InvariantCulture, "//{0}/Mappings/Mapping", sessionXpath));
            foreach (XPathNavigator mappingXml in mappings)
            {
                session.Mappings.Add((VersionControlMapping)MigrationUtil.LoadTypedData(mappingXml.OuterXml, typeof(VersionControlMapping)));
            }

            // Load Tfs
            XPathNavigator tfs = xml.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, "//{0}/Tfs", sessionXpath));
            session.Target = (TfsVersionControlTargetEndpoint)MigrationUtil.LoadTypedData(tfs.OuterXml, typeof(TfsVersionControlTargetEndpoint));

            // Load Source
            XPathNavigator source = xml.SelectSingleNode(string.Format(CultureInfo.InvariantCulture, "//{0}/Source", sessionXpath));
            MigrationProvider sourceProvider = MigrationConfiguration.Providers[session.ProviderId];
            session.Source = (IVersionControlEndpoint)sourceProvider.CreateInstance(typeof(IVersionControlEndpoint), source.InnerXml);

            // Load reports
            XPathNodeIterator reports = xml.Select(string.Format(CultureInfo.InvariantCulture, "//{0}/EventSinks/EventSink", sessionXpath));
            foreach (XPathNavigator reportXml in reports)
            {
                IMigrationSessionEventSink adaptor = LoadEventSinks(reportXml);
                session.RegisterEventSink(adaptor);
            }

            return session;
        }

        private static VersionControlSession createVcSessionInstance(XPathNavigator xml)
        {
            string providerId = GetAttributeFromCurrent(xml, "provider", true);

            Debug.Assert(MigrationConfiguration.Providers.ContainsKey(providerId));

            MigrationProvider sourceProvider = MigrationConfiguration.Providers[providerId];

            VersionControlSession session;

            IConfigFactory factory = sourceProvider.CreateFactoryInstance();

            session = (VersionControlSession)factory.CreateInstance(typeof(IMigrationProvider), null);
            if (session == null)
            {
                throw new InitializationException("Version control factory must create VersionControlSession types");
            }

            session.ProviderId = providerId;

            return session;
        }

        private static IMigrationSessionEventSink LoadEventSinks(XPathNavigator nav)
        {
            string initXml = nav.InnerXml;

            string eventSink = GetAttributeFromCurrent(nav, "provider", true);
            MigrationProvider prov = MigrationConfiguration.Providers[eventSink];
            return (IMigrationSessionEventSink)prov.CreateInstance(typeof(IMigrationSessionEventSink), initXml);
        }

        private static string GetAttributeFromCurrent(XPathNavigator xml, string attribute, bool throwIfNotFound)
        {
            if (xml.MoveToFirstAttribute())
            {
                do
                {
                    if (xml.LocalName == attribute)
                    {
                        string value = xml.InnerXml;
                        xml.MoveToParent();
                        return value;
                    }
                }
                while (xml.MoveToNextAttribute());

                xml.MoveToParent();
            }

            if (throwIfNotFound)
            {
                throw new InitializationException(
                    string.Format(MigrationToolkitVCResources.Culture,
                    MigrationToolkitVCResources.ConfigAttributeNotFound, attribute));
            }

            return string.Empty;
        }


        #region IDisposable Members

        protected virtual void Dispose(bool disposing)
        {
            if (!disposed)
            {
                if (disposing)
                {
                    foreach (IMigrationSession session in m_sessions.Values)
                    {
                        session.Dispose();
                    }
                    m_sessions.Clear();
                }

                // release unmanged resources
            }

            disposed = true;
        }

        public void Dispose()
        {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        #endregion

        #region IMigrationProvider Members

        /// <summary>
        /// Starts each session associated with the configuration.
        /// </summary>
        public void Start()
        {
            foreach (IMigrationSession session in this.Sessions.Values)
            {
                session.Start();
            }
        }

        /// <summary>
        /// Stop each session associated with the configuration
        /// </summary>
        public void Stop()
        {
            foreach (IMigrationSession session in this.Sessions.Values)
            {
                session.Stop();
            }
        }


        /// <summary>
        /// Aborts each running session associated with the configuration.
        /// </summary>
        public void Abort()
        {
            foreach (IMigrationSession session in this.Sessions.Values)
            {
                session.Abort();
            }
        }

        /// <summary>
        /// Start a one way synchronization on each session
        /// </summary>
        /// <param name="primarySystem"></param>
        public void Synchronize(SystemType primarySystem)
        {
            foreach (IMigrationSession session in this.Sessions.Values)
            {
                session.Synchronize(primarySystem);
            }
        }

        /// <summary>
        /// Start a one way synchronization on each session
        /// </summary>
        public void SynchronizeFull()
        {
            foreach (IMigrationSession session in this.Sessions.Values)
            {
                session.SynchronizeFull();
            }
        }

        #endregion

        public override void RegisterParent(ISettingsSection parent)
        {
            base.RegisterParent(parent);

            foreach (VersionControlSession session in m_sessions.Values)
            {
                session.RegisterParent(this);
            }
        }

        Dictionary<string, VersionControlSession> m_sessions = new Dictionary<string, VersionControlSession>();
        bool disposed;

    }
}
